package ssllink

import (
	"runtime"

	"slapp3/backend/sslink/proto"
	"slapp3/backend/sslink/tun"

	"github.com/gopacket/gopacket"
	"github.com/gopacket/gopacket/layers"
	"go.uber.org/zap"
)

var offset = 0 // reserve space for header

func (sl *Sslink) setupTun(cSess *ConnSession) error {
	if runtime.GOOS == "windows" {
		cSess.TunName = "SSLCon"
	} else if runtime.GOOS == "darwin" {
		cSess.TunName = "utun"
		offset = 4
	} else {
		cSess.TunName = "sslcon"
	}
	dev, err := tun.CreateTUN(cSess.TunName, cSess.MTU)
	if err != nil {
		sl.log.Error("Failed to create TUN device", zap.String("tunName", cSess.TunName), zap.Error(err))
		return err
	}
	if runtime.GOOS == "darwin" {
		cSess.TunName, _ = dev.Name()
	}

	tun.NativeTunDevice = dev.(*tun.NativeTun)

	// 不可并行
	err = sl.ConfigInterface(cSess)
	if err != nil {
		_ = dev.Close()
		return err
	}

	go sl.tunToPayloadOut(dev, cSess) // read from apps
	go sl.payloadInToTun(dev, cSess)  // write to apps
	return nil
}

// Step 3
// 网络栈将应用数据包转给 tun 后，该函数从 tun 读取数据包，放入 cSess.PayloadOutTLS 或 cSess.PayloadOutDTLS
// 之后由 payloadOutTLSToServer 或 payloadOutDTLSToServer 调整格式，发送给服务端
func (sl *Sslink) tunToPayloadOut(dev tun.Device, cSess *ConnSession) {
	// tun 设备读错误
	defer func() {
		_ = dev.Close()
	}()
	var (
		err error
		n   int
	)

	for {
		// 从池子申请一块内存，存放到 PayloadOutTLS 或 PayloadOutDTLS，在 payloadOutTLSToServer 或 payloadOutDTLSToServer 中释放
		// 由 payloadOutTLSToServer 或 payloadOutDTLSToServer 添加 header 后发送出去
		pl := getPayloadBuffer()
		n, err = dev.Read(pl.Data, offset) // 如果 tun 没有 up，会在这等待
		if err != nil {
			return
		}

		// 更新数据长度
		pl.Data = (pl.Data)[offset : offset+n]

		dSess := cSess.DSess
		if cSess.DtlsConnected.Load() {
			select {
			case cSess.PayloadOutDTLS <- pl:
			case <-dSess.CloseChan:
			}
		} else {
			select {
			case cSess.PayloadOutTLS <- pl:
			case <-cSess.CloseChan:
				return
			}
		}
	}
}

// Step 22
// 读取 tlsChannel、dtlsChannel 放入 cSess.PayloadIn 的数据包（由服务端返回，已调整格式），写入 tun，网络栈交给应用
func (sl *Sslink) payloadInToTun(dev tun.Device, cSess *ConnSession) {
	// tun 设备写错误或者cSess.CloseChan
	defer func() {
		if !cSess.Sess.ActiveClose {
			// vpnc.ResetRoutes(sl.) // 如果 tun 没有创建成功，也不会调用 SetRoutes // TODO
		}
		// 可能由写错误触发，和 tunToPayloadOut 一起，只要有一处确保退出 cSess 即可，否则 tls 不会退出
		// 如果由外部触发，cSess.Close() 因为使用 sync.Once，所以没影响
		cSess.Close()
		_ = dev.Close()
	}()

	var (
		err error
		pl  *proto.Payload
	)

	for {
		select {
		case pl = <-cSess.PayloadIn:
		case <-cSess.CloseChan:
			return
		}

		// 只有当使用域名分流且返回数据包为 DNS 时才进一步分析，少建几个协程
		if cSess.DynamicSplitTunneling {
			_, srcPort, _, _ := ResolvePacket(pl.Data)
			if srcPort == 53 {
				go sl.dynamicSplitRoutes(pl.Data, cSess)
			}
		}

		if offset > 0 {
			expand := make([]byte, offset+len(pl.Data))
			copy(expand[offset:], pl.Data)
			_, err = dev.Write(expand, offset)
		} else {
			_, err = dev.Write(pl.Data, offset)
		}

		if err != nil {
			return
		}

		// 释放由 serverToPayloadIn 申请的内存
		putPayloadBuffer(pl)
	}
}

func (sl *Sslink) dynamicSplitRoutes(data []byte, cSess *ConnSession) {
	packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
	dnsLayer := packet.Layer(layers.LayerTypeDNS)
	if dnsLayer != nil {
		dns, _ := dnsLayer.(*layers.DNS)

		query := string(dns.Questions[0].Name)

		if InArray(cSess.DynamicSplitIncludeDomains, query) {
			if _, ok := cSess.DynamicSplitIncludeResolved.Load(query); !ok && dns.ANCount > 0 {
				var answers []string
				for _, v := range dns.Answers {
					if v.Type == layers.DNSTypeA {
						answers = append(answers, v.IP.String())
					}
				}
				if len(answers) > 0 {
					cSess.DynamicSplitIncludeResolved.Store(query, answers)
					sl.DynamicAddIncludeRoutes(answers)
				}
			}
		} else if InArray(cSess.DynamicSplitExcludeDomains, query) {
			if _, ok := cSess.DynamicSplitExcludeResolved.Load(query); !ok && dns.ANCount > 0 {
				var answers []string
				for _, v := range dns.Answers {
					if v.Type == layers.DNSTypeA {
						answers = append(answers, v.IP.String())
					}
				}
				if len(answers) > 0 {
					cSess.DynamicSplitExcludeResolved.Store(query, answers)
					sl.DynamicAddExcludeRoutes(answers)
				}
			}
		}
	}
}
